#pragma once
#include <thread>
#include <vector>
#include <atomic>
#include <queue>
#include <map>
#include <functional>
#include <mutex>
#include <future>
#include <condition_variable>
using namespace std;

/*
	异步线程池和同步线程池的区别
		--同步线程池中，主线程如果想拿到任务函数的执行结果时需要阻塞等待任务函数返回，这样就处理不了其他事情
		--而异步线程池可以通过异步线程库的future对象来保存任务函数的返回值，这时主线程不需要阻塞等待，
			后面如果需要使用该任务函数的返回结果时只需调用future对象中的get()方法即可

	异步线程池构成：
		1.管理者线程 -> 子线程 1个
			-控制工作线程的数量：添加或者是减少
		2.若干工作线程 -> 子线程 n个
			-从任务队列中取任务，并处理
			-任务队列为空则阻塞（使用条件变量阻塞）
			-线程同步（互斥锁）
			-当前数量，空闲的线程数量，需要退出的线程数量（原子变量）
			-最小，最大线程数量（原子变量）
		3.任务队列 -> std::queue
		4.线程池开关 -> bool

*/

class ThreadPool
{
public:
	ThreadPool(int min = 4, int max = thread::hardware_concurrency());	//thread::hardware_concurrency() 会自动读取电脑的核心数
	~ThreadPool();

	//添加任务函数	-> 任务队列
	void add_task(function<void()> task);

	//添加任务函数 异步线程池的实现 泛型模板函数
	template <typename F,typename... Args>
	auto add_task(F&& f, Args&&... args) -> future<typename result_of<F(Args...)>::type>
	{
		//1.创建package_task实例
		using return_type = typename result_of<F(Args...)>::type;
		//使用智能指针来进行维护
		auto my_task = make_shared<packaged_task<return_type()>>(
			bind(forward<F>(f), forward<Args>(args)...)
			);
		//2.获取future
		future<return_type> res = my_task->get_future();
		//3.任务函数添加到任务队列 加锁
		m_queue_mx.lock();
		m_tasks.emplace([my_task]() { (*my_task)(); });
		m_queue_mx.unlock();

		m_cond.notify_one();

		return res;
	}

private:
	//管理线程任务函数
	void manager();

	//工作线程任务函数
	void worker();
private:
	thread* m_manager;						//管理者线程
	map<thread::id, thread> m_workers;		//工作线程
	vector<thread::id> m_ids;				//存储已经退出任务函数的线程id
	queue<function<void()>> m_tasks;		//任务队列

	atomic<int> m_min_thread;				//最小线程数量
	atomic<int> m_max_thread;				//最大线程数量
	atomic<int> m_cur_thread;				//当前线程数量
	atomic<int> m_exit_thread;				//需要退出的线程数量
	atomic<int> m_idle_thread;				//空闲线程数量
	atomic<bool> m_shop;					//线程池开关
	mutex m_queue_mx;						//互斥锁 (锁任务队列的)
	mutex m_ids_mx;							//互斥锁 （锁ids数组的）
	condition_variable m_cond;				//条件变量（用来阻塞/唤醒消费者线程）
};
